#coding: utf8
from Jugador import Jugador
from Carta import Carta
from Mazo import Mazo

class JuegoCorazones(object):
	"""Clase que representa un juego de Corazones"""

	def __init__(self, jugadores):
		"""Crea un juego en base a 4 jugadores."""
		self.jugadores = jugadores
		self.puntajes = [0,0,0,0]
		self.cartas_juntadas=[[],[],[],[]]

	def termino(self):
		"""Devuelve True si alguno de los jugadores alcanzo los 100 puntos"""
		for puntaje in self.puntajes:
			if puntaje >=100:
				return True
		return False

	def imprimir_puntajes(self):
		"""Imprime los puntajes de cada jugador hasta el momento"""
		puntajes=""
		for jugador in self.jugadores:
			puntajes+="%s : %d" % (str(jugador),self.puntajes[jugador.obtener_id_jugador()])+"\n\r"
		print puntajes

	def barajar(self):
		"""Crea un mazo nuevo, lo mezcla y le reparte una carta a cada jugador hasta
		que el mismo queda vacio."""
		#Creacion del mazo
		mazo = Mazo()
		mazo.mezclar()
		#Repartir cartas
		for ronda in xrange(13):
			for jugador in self.jugadores:
				jugador.recibir_carta(mazo.obtener_tope())

	def identificar_jugador_que_inicia(self):
		"""Se fija cual de los 4 jugadores es primero y devuelve su id."""
		for jugador in self.jugadores:
			if jugador.es_primero():
				return jugador.obtener_id_jugador()

	def identificar_jugador_que_perdio(self, cartas_jugadas, id_primero):
		"""Recibe las 4 cartas jugadas en la mano y el id del jugador que abrio
		la jugada. Devuelve el id del jugador que perdio.
		Pierde el jugador que juega la carta mas alta del palo con el que inicio
		la jugada el primer jugador.
		Las cartas por orden creciente son: 2, 3,..., 10, J, Q, K, A."""
		#En caso de que la primera sea el "AS" directamente devuelve el id_primero
		if cartas_jugadas[0].obtener_numero()==1:
			return id_primero
		# Se toma como candidata a más alta a la primer carta jugada (que es seguro que será del palo en juego)
		palo = cartas_jugadas[0].obtener_palo()
		numero = cartas_jugadas[0].obtener_numero()
		id_perdedor=id_primero
		# Se itera por las 3 restantes, y si es del mismo palo se comparan los numeros
		for i in xrange(1,4):
			if cartas_jugadas[i].obtener_palo() == palo:
				#Si es el "AS" es la mas alta, por lo tanto se devuelve directamente
				if cartas_jugadas[i].obtener_numero() == 1:
					return (id_primero+i)%4
				elif cartas_jugadas[i].obtener_numero() > numero: 
					numero = cartas_jugadas[i].obtener_numero()
					id_perdedor = (id_primero+i)%4
		return id_perdedor
			
			
	def procesar_e_informar_resultado(self, cartas_jugadas, id_primero, id_perdedor):
		"""Recibe las cartas de la jugada, el id del primer jugador, y el id del
		jugador que perdio.
		Almacena lo necesario para llevar la cuenta de puntos e informa a todos
		los jugadores del resultado de la jugada."""
		self.cartas_juntadas[id_perdedor] += cartas_jugadas
		#Entrega de informes a cada jugador
		for jugador in self.jugadores:
			jugador.conocer_jugada(cartas_jugadas, id_primero, id_perdedor)
		
	def hay_corazones(self, cartas):
		"""Devuelve True si hay algun corazon entre las cartas pasadas"""
		for carta in cartas:
			if carta.obtener_palo()== Carta.CORAZONES:
				return True
		return False

	def realizar_jugada(self, nro_mano, nro_jugada, id_primero, corazon_jugado):
		"""Recibe el numero de mano, de jugada el id del primer jugador y si ya
		se jugaron corazones hasta el momento.
		Hace jugar una carta a cada uno de los jugadores empezando por el primero.
		Devuelve las 4 cartas jugadas."""
		cartas_jugadas=[]
		id_actual=id_primero
		for turno in xrange (4):
			carta=self.jugadores[id_actual].jugar_carta(nro_mano, nro_jugada, cartas_jugadas, corazon_jugado)
			cartas_jugadas.append(carta)
			id_actual=(id_actual+1)%4
		return cartas_jugadas
		
	def calcular_puntajes(self):
		"""Al finalizar la mano, calcula y actualiza los puntajes de los jugadores.
		Cada jugador suma un punto por cada corazon levantado en la mano y 13 puntos
		si levanto la Q de picas, salvo en el caso de que un solo jugador haya
		levantado todos los corazones y la Q de picas, caso en el cual todos los
		jugadores salvo el suman 26 puntos."""
		alcanzo_el_cielo = False
		id_actual = 0
		while id_actual<4 and alcanzo_el_cielo!=True:
			suma_de_puntos=0
			for carta in self.cartas_juntadas[id_actual]:
				if carta.obtener_palo()== Carta.CORAZONES:
					suma_de_puntos+=1
				elif carta.obtener_palo()== Carta.PICAS and carta.obtener_numero()==12:
					suma_de_puntos+=13
			if suma_de_puntos == 26:
				alcanzo_el_cielo = True
			else:
				self.puntajes[id_actual]+=suma_de_puntos
				id_actual+=1
		#En caso de que salio del ciclo porque alguien alcanzo el cielo, se le suma 26 a todos los demas		
		if alcanzo_el_cielo:
			for id_jugador in xrange(4):
				if id_jugador!=id_actual:
					self.puntajes[id_jugador]+=26
		#Luego de hacer las cuentas (y de que termina la mano), los jugadores no poseen cartas juntadas
		self.cartas_juntadas=[[],[],[],[]]
		
	def intercambiar_cartas(self, nro_mano):
		"""Antes de hacer la primer jugada se pasan 3 cartas entre los rivales.
		En la primer mano, las cartas se pasan al jugador de la izquierda; en la
		segunda al jugador de la derecha; en la tercera al jugador del frente y
		en la cuarta no se pasan cartas. A partir de la quinta mano, se repite el
		mismo ciclo.
		El metodo debe primero pedirle las 3 cartas a pasar a cada oponente y luego
		entregarle las cartas que le fueron pasadas."""
		#Numero guia para el sentido de paso de las cartas (cuando la jugada sea multiplo de 4, nro_guia=0)
		nro_guia = nro_mano%4
		#Si el numero de mano es multiplo de 4, no se pasan cartas.
		if nro_guia == 0:
			return
		#Si no es multiplo de 4, se reciben las cartas y luego se realiza el intercambio
		cartas_a_intercambiarse=[]
		#Pide las 3 cartas a pasar a cada oponente
		for id_jugador in xrange (4):
			cartas_a_intercambiarse.append(self.jugadores[id_jugador].devolver_cartas_a_pasar(id_jugador))
		#Se le entregan a cada jugador las cartas de jugador anterior segun la ronda
		for jugador in self.jugadores:
			if nro_guia == 1:
				#Se pasan cartas en sentido horario
				id_jugador_ant=(jugador.obtener_id_jugador()-1)%4				
			elif nro_guia == 2:
				#Se pasan cartas en sentido antihorario
				id_jugador_ant=(jugador.obtener_id_jugador()+1)%4
			elif nro_guia == 3:
				#Se pasan cartas de forma intercalada
				id_jugador_ant=(jugador.obtener_id_jugador()+2)%4
			#El jugador recibe las cartas del anterior
			(self.jugadores[jugador.obtener_id_jugador()]).recibir_cartas_pasadas(id_jugador_ant, cartas_a_intercambiarse[id_jugador_ant])
				
	def ganadores(self):
		"""Una vez terminado el juego, devuelve la lista de ganadores.
		Son ganadores todos los jugadores que hayan alcanzado el menor puntaje."""
		mejor_puntaje = 100
		#Se agregan a ganadores el/los jugadores con puntajes mas bajos
		for id_jugador in xrange(4):
			puntaje = self.puntajes[id_jugador]
			if puntaje < mejor_puntaje:
				mejor_puntaje = puntaje
				ganadores = [self.jugadores[id_jugador]]
			elif puntaje == mejor_puntaje and mejor_puntaje<100:
				ganadores.append(self.jugadores[id_jugador])
		#Devolucion de ganadores
		return ganadores
				
	def jugar_mano(self, nro_mano):
		"""Realiza las 13 jugadas que corresponden a una mano completa."""
		corazon_jugado = False
		id_primero = self.identificar_jugador_que_inicia()

		# INICIO: Chequeos de trampa
		palos_faltantes = [[], [], [], []]
		cartas_en_mesa = []
		solo_tiene_corazones = [False] * 4
		# FIN: Chequeos de trampa

		for nro_jugada in xrange(1, 13 + 1):
			print "Jugada %i" % nro_jugada
			print "Empieza %s" % self.jugadores[id_primero]

			cartas_jugadas = self.realizar_jugada(nro_mano, nro_jugada, id_primero, corazon_jugado)
				
			id_perdedor = self.identificar_jugador_que_perdio(cartas_jugadas, id_primero)
			print "Levanta %s" % self.jugadores[id_perdedor]

			self.procesar_e_informar_resultado(cartas_jugadas, id_primero, id_perdedor)


			# INICIO: Chequeos de trampa
			if nro_jugada == 1 and not cartas_jugadas[0] == Carta(2, Carta.TREBOLES):
				raise Exception("El primer jugador no jugo el 2 de treboles""")
			if nro_jugada == 1 and (corazon_jugado or Carta(12, Carta.PICAS) in cartas_jugadas):
				raise Exception("Jugador jugo carta especial en primer juego""")
			for i in xrange(4):
				if cartas_jugadas[i].obtener_palo() in palos_faltantes[(i + id_primero) % 4]:
					raise Exception("El jugador %s dijo que no tenia %s" % (self.jugadores[(i + id_primero) % 4], Carta.PALOS[cartas_jugadas[i].obtener_palo()]))
				if solo_tiene_corazones[(i + id_primero) % 4] and cartas_jugadas[i].obtener_palo() != Carta.CORAZONES:
					raise Exception("El jugador %s dijo que solo tenia %s" % (self.jugadores[(i + id_primero) % 4], Carta.PALOS[Carta.CORAZONES]))
			palo_jugada = cartas_jugadas[0].obtener_palo()
			for i in xrange(1, 4):
				if cartas_jugadas[i].obtener_palo() != palo_jugada and palo_jugada not in palos_faltantes[(i + id_primero) % 4]:
					palos_faltantes[(i + id_primero) % 4].append(palo_jugada)
			for carta in cartas_jugadas:
				if carta in cartas_en_mesa:
					raise Exception("Alguien se matufio el %s" % carta)
				cartas_en_mesa.append(carta)
			if not corazon_jugado and palo_jugada == Carta.CORAZONES:
				solo_tiene_corazones[id_primero] = True
			# FIN: Chequeos de trampa


			corazon_jugado = corazon_jugado or self.hay_corazones(cartas_jugadas)

			id_primero = id_perdedor

	def jugar(self):
		"""Juega una partida completa de Corazones."""
		nro_mano = 1
		while not self.termino():
			print "Mano %d" % nro_mano
			self.barajar()
			self.intercambiar_cartas(nro_mano)
			self.jugar_mano(nro_mano)
			self.calcular_puntajes()
			self.imprimir_puntajes()

			nro_mano += 1
